## pyinstaller -F -w .\视频快速截图.py
import cv2, json
from PIL import Image, ImageTk
import tkinter as tk
from tkinter import Label, Button, filedialog
class VideoPlayer:
    def __init__(self, root, video_source=0, scale_factor=0.5):
        self.root = root
        self.root.title("视频快速截图")

        self.scale_factor = scale_factor
        
        self.video_source = video_source
        
        self.is_paused = False
        self.is_running = True

        self.canvas = tk.Canvas(root, width=640, height=480)
        self.canvas.pack()
        # 绑定鼠标事件
        self.canvas.bind("<Button-1>", self.on_button_press)
        self.canvas.bind("<B1-Motion>", self.on_drag)
        self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
        self.rect = None  # 用于存储当前绘制的矩形

        self.open_button = tk.Button(root, text="Open Video", command=self.open_video)
        self.open_button.pack(side=tk.LEFT)
        
        self.stop_button = Button(root, text="结束", command=self.stop_video)
        self.stop_button.pack(side=tk.LEFT)
        
        self.play_button = Button(root, text="播放", command=self.play_video)
        self.play_button.pack(side=tk.LEFT)
        
        self.pause_button = Button(root, text="暂停", command=self.pause_video)
        self.pause_button.pack(side=tk.LEFT)
        
        self.pause_button = Button(root, text="截图", command=self.screenshot_video)
        self.pause_button.pack(side=tk.LEFT)

        self.slider = tk.Scale(root, from_=0, to_=100, orient=tk.HORIZONTAL, command=self.update_slider)
        self.slider.pack(side=tk.LEFT)

        self.label = tk.Label(root, text="00:00:00 / 00:00:00")
        self.label.pack(side=tk.LEFT)
        
        self.infomation_label = Label(root, text="健康忠告：请注意休息，合理安排时间。")
        self.infomation_label.pack(side=tk.LEFT)
        
        self.frameCount = 0
        self.data = {
            "time": [],
            "range": []
        }
        self.rectangleColor = "Black"
        self.rectangleTime = 0
        self.rectangleLineWidth = 2
        #self.update_frame()
    
    def viedo_info(self):
        # 获取视频的原始尺寸
        self.frame_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        self.frame_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

        # 获取帧率
        #self.fps = self.cap.get(cv2.CAP_PROP_FPS)  # 保留帧率的浮点数形式
        #self.fps_ms = int(1000 / self.fps)  # 计算每帧时间的浮点数形式
        self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
        self.frame_rate = int(self.cap.get(cv2.CAP_PROP_FPS))
        self.fps_ms = 2
        
        # 计算缩放后的尺寸
        self.scaled_width = int(self.frame_width * scale_factor)
        self.scaled_height = int(self.frame_height * scale_factor)
    
    def update_frame(self):
        if self.is_running and not self.is_paused:
            ret, self.frame = self.cap.read()
            if not ret:
                self.stop_video()
                return
            
            self.current_frame = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES))
            if self.current_frame % self.frame_rate == 0:
                self.slider.set(self.current_frame)
            self.update_label(self.current_frame)

            # 缩放帧
            self.scaled_frame = cv2.resize(self.frame, (self.scaled_width, self.scaled_height))
            
            self.scaled_frame = cv2.cvtColor(self.scaled_frame, cv2.COLOR_BGR2RGB)
            img = Image.fromarray(self.scaled_frame)
            imgtk = ImageTk.PhotoImage(image=img)

            # 在 canvas 上显示图像
            # 注意：我们需要保持对 imgtk 的引用，否则它会被垃圾回收
            self.image_ref = imgtk  # 保持对 PhotoImage 的引用
            # 在Canvas上显示图片
            # 你可以使用create_image方法，并指定图片的x和y坐标
            # 以及图片的宽度和高度（可选，如果省略则保持原尺寸）
            self.canvas.delete(tk.ALL)
            self.canvas.create_image(0, 0, image=self.image_ref, anchor=tk.NW)
            self.draw_rectangle()
            self.frameCount += 1
            if self.rectangleTime != 0:
                if self.rectangleTime < 30:
                    self.rectangleTime += 1
                else:
                    self.rectangleColor = "Black"
                    self.rectangleLineWidth = 2
                    self.rectangleTime = 0
            self.root.after(self.fps_ms, self.update_frame)
    
    def play_video(self):
        self.is_paused = False
        self.update_frame()
    
    def pause_video(self):
        self.is_paused = True
        if self.scaled_frame is not None:
            self.draw_dots(self.canvas)
        else:
            print("Failed to capture the frame.")
    
    def screenshot_video(self):
        if self.frame is not None:
            sx = int(self.start_x / self.scale_factor)
            sy = int(self.start_y / self.scale_factor)
            ex = int(self.end_x / self.scale_factor)
            ey = int(self.end_y / self.scale_factor)
            cropped_frame = self.frame[sy:ey, sx:ex]
            image_name = ".".join(self.video_source.split(".")[:-1]) + ".screenshot." + str(self.frameCount) + ".png"
            # 保存截图
            cv2.imwrite(image_name, cropped_frame)
            # 显示截图（可选）
            self.data["time"].append(self.frameCount)
            self.data["range"].append([sx, sy, ex, ey])
            self.infomation_label.config(text="保存到截图："+image_name)
            self.rectangleTime = 1
            self.rectangleColor = "Red"
            self.rectangleLineWidth = 9
        else:
            print("Failed to capture the frame.")

    def stop_video(self):
        self.is_running = False
        self.save_json(self.data)
        self.cap.release()
        self.root.destroy()
    
    def draw_dots(self, canvas, interval=10, color='gray'):
        # 获取画布的大小
        canvas_width = canvas.winfo_width()
        canvas_height = canvas.winfo_height()
 
        # 遍历画布的每个点，每隔 interval 画一个点
        for x in range(0, canvas_width, interval):
            for y in range(0, canvas_height, interval):
                # 使用 create_oval 方法画一个圆形点
                # 参数分别是：x1, y1, x2, y2, fill=color
                canvas.create_oval(x, y, x + interval // 5, y + interval // 5, fill=color)
    
    def on_button_press(self, event):
        # 记录下鼠标按下的坐标作为矩形的起始点
        self.start_x, self.start_y = self.grid_position(event.x, event.y)

    def on_drag(self, event):
        # 如果已经有起始点，则根据鼠标当前位置和起始点绘制矩形
        if self.start_x is not None and self.start_y is not None:
            # 如果已经绘制了一个矩形，先删除它
            if self.rect is not None:
                self.canvas.delete(self.rect)

            end_x, end_y = self.grid_position(event.x, event.y)
            # 绘制新的矩形
            self.rect = self.canvas.create_rectangle(
                self.start_x, self.start_y, end_x, end_y,
                outline='black', width=2
            )
            text = str(self.start_x) + "," + str(self.start_y) + "," + str(end_x) + "," + str(end_y)
            self.infomation_label.config(text="截图区域：" + text)

    def on_button_release(self, event):
        # 释放鼠标时，可以记录最终的矩形或者重置状态等
        # 这里我们只是保留最终的矩形状态
        self.end_x, self.end_y = self.grid_position(event.x, event.y)

    def draw_rectangle(self):
        if self.start_x is not None and self.start_y is not None and self.end_x is not None and self.end_y is not None:
            if self.rect is not None:
                self.canvas.delete(self.rect)
            self.rect = self.canvas.create_rectangle(
                self.start_x, self.start_y, self.end_x, self.end_y,
                outline=self.rectangleColor, width=self.rectangleLineWidth
            )

    def grid_position(self, x, y, interval=10):
        gx = int(x / interval) * interval
        gy = int(y / interval) * interval
        return gx, gy
    
    def save_json(self, data):
        fileName = ".".join(self.video_source.split(".")[:-1]) + '.data.json'
        # 使用 json.dump() 将字典直接写入文件
        with open(fileName, 'w', encoding='utf-8') as f:  # 指定文件编码为 utf-8
            json.dump(data, f, indent=4, ensure_ascii=False)  # ensure_ascii=False 保留非 ASCII 字符

    def __del__(self):
        # 当对象被销毁时，释放视频捕获对象
        if self.cap.isOpened():
            self.cap.release()
    
    def update_slider(self, val):
        if self.cap and self.cap.isOpened():
            frame_number = int(val)
            self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
            self.current_frame = frame_number
            #self.update_frame()
    
    def update_label(self, current_frame):
        duration = self.total_frames / self.frame_rate
        current_time = current_frame / self.frame_rate
        minutes, seconds = divmod(current_time, 60)
        minutes_int, seconds_frac = divmod(seconds, 1)
        seconds = int(seconds_frac * 100) / 100
        duration_minutes, duration_seconds = divmod(duration, 60)
        duration_minutes_int, duration_seconds_frac = divmod(duration_seconds, 1)
        duration_seconds = int(duration_seconds_frac * 100) / 100
 
        current_time_str = f"{int(minutes):02}:{int(minutes_int):02}.{seconds:02}"
        duration_str = f"{int(duration_minutes):02}:{int(duration_minutes_int):02}.{duration_seconds:02}"
        self.label.config(text=f"{current_time_str} / {duration_str}")
    
    def open_video(self):
        self.video_source = filedialog.askopenfilename(filetypes=[("Video Files", "*.mp4 *.avi *.mov")])
        if self.video_source:
            self.cap = cv2.VideoCapture(self.video_source)
            self.frame_rate = int(self.cap.get(cv2.CAP_PROP_FPS))
            self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
            self.slider.config(to_=self.total_frames - 1)
            self.update_label(0)
            # 获取视频的原始尺寸
            self.frame_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
            self.frame_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

            # 获取帧率
            #self.fps = self.cap.get(cv2.CAP_PROP_FPS)  # 保留帧率的浮点数形式
            #self.fps_ms = int(1000 / self.fps)  # 计算每帧时间的浮点数形式
            self.fps_ms = 2

            # 计算缩放后的尺寸
            self.scaled_width = int(self.frame_width * scale_factor)
            self.scaled_height = int(self.frame_height * scale_factor)

            # 调整 canvas 大小以匹配视频帧
            self.canvas.config(width=self.scaled_width, height=self.scaled_height)
            self.slider.config(to_=self.total_frames - 1)
            self.start_x = 0  # 矩形的起始X坐标
            self.start_y = 0  # 矩形的起始Y坐标
            self.end_x = self.scaled_width
            self.end_y = self.scaled_height

            self.play_video()
            self.pause_video()

if __name__ == "__main__":
    root = tk.Tk()
    # 设置缩放因子，例如0.5表示原始尺寸的50%
    scale_factor = 0.7
    # 可以是摄像头索引或视频文件路径
    video_source = "视频快速截图.mp4"  # 或者 0（表示默认摄像头）
    player = VideoPlayer(root, video_source, scale_factor)
    root.mainloop()